﻿using DefaultDspPluginsLib;
using DefaultDspPluginsLib.Depricated;
using SharpDepend.Classes;
using SoundEngine3;
using System;
using System.Runtime.InteropServices;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.HighLevel
{
    [ModuleDisplay("Oscilator")]
    public class OscilatorModule : BaseModule
    {
#if !Portable
        [DllImport(NativeSoundEngineDllImport.Dll)]
        public static extern void Common_Modulizer_Oscilator_Process(int length, int waveformBufferLength, ref double currentSample
            , double[] pitch, double[] frequency, double[] volyme, double[] tune
            , double[] waveformBuffer, double[] outputBuffer, double[] runAndIdleAmp, double[] mCurrentSampleRestartAt
            , double sampleRate);
#endif

        private MidiMessageToWave mMidiToWave;
        
        public readonly ModuleInput InTuneController;
        public readonly ModuleInput InPitch;
        public readonly ModuleInput InRunAndIdle;
        public readonly ModuleInput InWaveform;

        public readonly InputModuleParameter ParameterVolyme;
        public readonly InputModuleParameter ParameterFrequency;

        public readonly ModuleOutput Output;
        
        private double n_currentSample;
        private int mProcessRunning;
        private double[] mRunAndIdleAmp;
        private double[] mToneNext;
        private int mLaststart;
        private int mLaststop;
        private double[] mCurrentSampleRestartAt;
        
        public OscilatorModule(ModuleManager manager)
            : base(manager)
        {
            mMidiToWave = new MidiMessageToWave(manager.SoundEngineContext);

            int bufferSize = SoundEngineContext.BufferSize;

            mRunAndIdleAmp = new double[bufferSize];
            mToneNext = new double[bufferSize];
            mCurrentSampleRestartAt = new double[bufferSize];

            mDefaultRunAndIndle = new double[bufferSize];
            mDefaultTune = new double[bufferSize];
            
            AddInput(InTuneController = new ModuleInput("Tun", "Tune", IOType.Audio, this));
            AddInput(InPitch = new ModuleInput("Pch", "Pitch", IOType.Midi, this));
            AddInput(InRunAndIdle = new ModuleInput("RaI", "RunAndIdle", IOType.Midi, this));
            AddInput(InWaveform = new ModuleInput("Wav", "Waveform", IOType.Waveform, this));

            AddOutput(Output = new ModuleOutput("Out", "Output", IOType.Audio, this));

            ParameterVolyme = new InputModuleParameter(this, "Amp", 0.4, IOType.Audio);
            ParameterVolyme.Label = "Amplitude";
            ParameterVolyme.MinValue = 0;
            ParameterVolyme.MaxValue = 1;
            AddModuleParameter(ParameterVolyme);

            ParameterFrequency = new InputModuleParameter(this, "Frq", 3000, IOType.Audio);
            ParameterFrequency.Label = "Frequency";
            ParameterFrequency.MinValue = 1;
            ParameterFrequency.MaxValue = 18000;
            AddModuleParameter(ParameterFrequency);
        }
        
        double[] mDefaultRunAndIndle = null;
        double[] mDefaultTune = null;

        public override void ChangeBufferSize(int bufferSize)
        {
            mRunAndIdleAmp = new double[bufferSize];
            mToneNext = new double[bufferSize];
            mCurrentSampleRestartAt = new double[bufferSize];

            mDefaultRunAndIndle = new double[bufferSize];
            mDefaultTune = new double[bufferSize];
        }

        public override void Process()
        {
            ProcessRunAndIdle();

            ProcessFrequency();

            if (InWaveform.IsConnected)
            {
                ModuleOutput output = Output;
                
                WaveFormBuffer waveform = InWaveform.FromOutput.AudioBuffer;
                double[] outputBuffer = Output.AudioBuffer.Buffer;
                double[] runAndIdleAmp = mRunAndIdleAmp;
                double[] tune = InTuneController.IsConnected ? InTuneController.FromOutput.AudioBuffer.Buffer : mDefaultTune;
                double[] volyme = ParameterVolyme.Values;
                double[] frequency = ParameterFrequency.Values;
                double[] pitch = InPitch.IsConnected ? mToneNext : null;
                bool isPitch = pitch != null;
                double sampleRate = SoundEngineContext.SampleRatePitch(44100);
                int length = Output.AudioBuffer.Length;
                double[] waveformBuffer = waveform.Buffer;

                if (NativeSoundEngineDllImport.UseNativeProcessing)
                {
#if !Portable
                    Common_Modulizer_Oscilator_Process(length, waveformBuffer.Length, ref n_currentSample
                    , pitch, frequency, volyme, tune, waveformBuffer, outputBuffer, runAndIdleAmp, mCurrentSampleRestartAt, sampleRate);
#endif
                }
                else
                {
                    Common_Modulizer_Oscilator.Process(length, waveformBuffer.Length, ref n_currentSample
                    , pitch, frequency, volyme, tune, waveformBuffer, outputBuffer, runAndIdleAmp, mCurrentSampleRestartAt, sampleRate);
                }
            }
            else
            {
                Output.AudioBuffer.Clear();
            }
        }

        private void ProcessFrequency()
        {
            if (InPitch.IsConnected)
            {
                // Begin process midi.
                mMidiToWave.ProcessBegin(InPitch.FromOutput.MidiBuffer);

                //ProcessFrequencyBackward();

                ProcessFrequencyForward();

                // End process midi.
                mMidiToWave.ProcessEnd();
            }
        }

        private void ProcessFrequencyForward()
        {
            // Loop all ongoing midi notes.
            if (mMidiToWave.Count > 0)
            {
                // Reset array to 0 so we can later fill 0 with latest frequency at the bottom of this method.
                PeformanceHelper.MemSet(mToneNext, 0, mToneNext.Length, 0);

                for (int i = 0; i < mMidiToWave.Count; i++)
                {
                    var midi = mMidiToWave[i];

                    if (midi.State == MidiMessageToWave.NoteState.Running)
                    {
                        PeformanceHelper.MemSet(mToneNext, 0, mToneNext.Length, midi.NoteKey);
                    }
                    else if (midi.State == MidiMessageToWave.NoteState.Start)
                    {
                        if ((int)midi.DeltaFrameOff != mToneNext.Length - 1)
                        {
                            //throw new Exception("(int)midi.DeltaFrameOff must be mToneNext.Length-1 otherwise NoteState.Start would be meaningless.");
                            midi.DeltaFrameOff = mToneNext.Length - 1;
                        }

                        int startIndex = (int)midi.DeltaFrameOn;

                        // Restart sample time.
                        if (mMidiToWave.Count == 1)
                        {
                            mCurrentSampleRestartAt[startIndex] = 1.0;
                        }

                        int off = mToneNext.Length - startIndex;
                        PeformanceHelper.MemSet(mToneNext, startIndex, off, midi.NoteKey);
                    }
                    else if (midi.State == MidiMessageToWave.NoteState.Stop)
                    {
                        if ((int)midi.DeltaFrameOn != 0)
                        {
                            //throw new Exception("(int)midi.DeltaFrameOn must be 0 otherwise NoteState.Stop would be meaningless.");
                            midi.DeltaFrameOn = 0;
                        }

                        int stopIndex = (int)midi.DeltaFrameOff;

                        int off = stopIndex;
                        PeformanceHelper.MemSet(mToneNext, 0, off, latestFrequency); // midi.NoteKey...
                    }
                    else if (midi.State == MidiMessageToWave.NoteState.Middle)
                    {
                        int startIndex = (int)midi.DeltaFrameOn;
                        int stopIndex = (int)midi.DeltaFrameOff;

                        // Restart sample time.
                        if (mMidiToWave.Count == 1)
                        {
                            mCurrentSampleRestartAt[startIndex] = 1.0;
                        }

                        int off = stopIndex - startIndex;
                        PeformanceHelper.MemSet(mToneNext, startIndex, off, midi.NoteKey);
                    }
                }

                // Fill 0 for the array with latest frequency.
                for (int i = 0; i < mToneNext.Length; i++)
                {
                    double freq = mToneNext[i];
                    if (freq == 0.0)
                    {
                        mToneNext[i] = latestFrequency;
                    }
                    else
                    {
                        latestFrequency = freq;
                    }
                }
            }
            else
            {
                // Always fill the array with latest frequency.
                // TODO: Maybe just do this only once.
                PeformanceHelper.MemSet(mToneNext, 0, mToneNext.Length, latestFrequency);
            }
        }

        double latestFrequency = 0;

        private void ProcessFrequencyBackward()
        {
            int lastStart = mToneNext.Length - 1;

            // Loop all ongoing midi notes.
            if (mMidiToWave.Count > 0)
            {
                for (int i = mMidiToWave.Count - 1; i > -1; --i)
                {
                    var midi = mMidiToWave[i];

                    if (midi.State == MidiMessageToWave.NoteState.Running)
                    {
                        PeformanceHelper.MemSet(mToneNext, 0, mToneNext.Length, midi.NoteKey);
                    }
                    else if (midi.State == MidiMessageToWave.NoteState.Start)
                    {
                        int startIndex = (int)midi.DeltaFrameOn;

                        // Restart sample time.
                        mCurrentSampleRestartAt[startIndex] = 1.0;

                        PeformanceHelper.MemSet(mToneNext, startIndex, mToneNext.Length - startIndex, midi.NoteKey);

                        lastStart = startIndex;
                    }
                    else if (midi.State == MidiMessageToWave.NoteState.Stop)
                    {
                        int startIndex = (int)midi.DeltaFrameOn;

                        int off = Math.Min(mToneNext.Length - startIndex, lastStart - startIndex);
                        PeformanceHelper.MemSet(mToneNext, startIndex, off, midi.NoteKey);

                        lastStart = startIndex;
                    }
                    else if (midi.State == MidiMessageToWave.NoteState.Middle)
                    {
                        int startIndex = (int)midi.DeltaFrameOn;
                        int stopIndex = (int)midi.DeltaFrameOff;

                        // Restart sample time.
                        mCurrentSampleRestartAt[startIndex] = 1.0;

                        PeformanceHelper.MemSet(mToneNext, startIndex, stopIndex - startIndex, midi.NoteKey);

                        lastStart = startIndex;
                    }
                }
            }
        }

        private void ProcessRunAndIdle()
        {
            if (InRunAndIdle.IsConnected)
            {
                Array.Clear(mRunAndIdleAmp, 0, SoundEngineContext.BufferSize);
                bool unChanged = true;

                mLaststart -= SoundEngineContext.BufferSize;
                mLaststop -= SoundEngineContext.BufferSize;

                // Check if this buffer process has midi signaled.
                if (InRunAndIdle.FromOutput.HasGotMidiMessage())
                {
                    for (int i = 0; i < InRunAndIdle.FromOutput.AudioBuffer.Length; i++)
                    {
                        double signal = InRunAndIdle.FromOutput.AudioBuffer[i];

                        if (signal == ModuleIO.NOTE_ON)
                        {
                            if (mProcessRunning == 0)
                            {
                                mLaststart = i;

                                // First start.
                                PeformanceHelper.MemSet(mRunAndIdleAmp, i, mRunAndIdleAmp.Length - i, 1.0);

                                unChanged = false;
                            }

                            mProcessRunning++;
                        }
                        else if (signal == ModuleIO.NOTE_OFF)
                        {
                            mProcessRunning--;
                            if (mProcessRunning < 0)
                            {
                                //throw new Exception("Unexpected processing state in oscilator.");
                                mProcessRunning = 0;

                                // User problably added a module while a note is running.
                                continue;
                            }

                            if (mProcessRunning == 0)
                            {
                                // Finish the last note until stop if it was on the last buffer.
                                if (mLaststart < 0)
                                {
                                    PeformanceHelper.MemSet(mRunAndIdleAmp, 0, i, 1.0);
                                }

                                mLaststop = i;

                                // Last stop.
                                Array.Clear(mRunAndIdleAmp, i, SoundEngineContext.BufferSize - i);

                                unChanged = false;
                            }
                        }
                    }
                }

                if (unChanged && mProcessRunning == 0)
                {
                    // Running off.
                    PeformanceHelper.MemSet(mRunAndIdleAmp, 0, mRunAndIdleAmp.Length, 0.0);
                }
                else if (unChanged && mProcessRunning >= 1)
                {
                    // Running on.
                    PeformanceHelper.MemSet(mRunAndIdleAmp, 0, mRunAndIdleAmp.Length, 1.0);
                }
            }
            else
            {
                mProcessRunning = 0;

                // Running on.
                PeformanceHelper.MemSet(mRunAndIdleAmp, 0, mRunAndIdleAmp.Length, 1.0);
            }
        }
    }
}
